#!/usr/bin/env python
#
# Copyright 2009 Jonathan Perkins

"""Selects cards for playing Dominion.

<set>               - One of b (Base), i (Intrigue), p (Promo), or s (Seaside).
<card index>        - The index of the card within the given set.
<radio>             - 0 for None, 1 for Maybe, 2 for Required.

<set><card index>   - Indicates a card is available for selection.
k<set><card index>  - Keep a card in the next set.
of<set><card index>,<set><card index>,...
                    - Forces the given set.  Used by the previous set button.
os<set><card index> - Indicates the card was in the previous set.
ror                 - Requires no repeat cards (checkbox).
rc<cost><radio>     - A radio to require cards of the given cost.
rl<plus><radio>     - A radio to require cards of the given plus.
rpa<radio>          - A radio to require plain action cards.
rt<type><radio>     - A radio to require cards of the given type.
"""

import datetime
import logging
import math
import random
import sys
from google.appengine.api import users
from google.appengine.ext import webapp
from google.appengine.ext.webapp import util

import cards
import data
import set_editor


# Various constants
_NUM_COLS = 3
_URI = 'http://dominioncards.appspot.com'

# Option and set initials are in the same namespace, and must not overlap.
_INITIAL_KEEP = 'k'
_INITIAL_OPTION = 'o'
_INITIAL_REQUIREMENT = 'r'

_MIN_COST = 2
_MAX_COST = 6

_RADIO_NONE = 0
_RADIO_MAYBE = 1
_RADIO_REQUIRED = 2


def _CheckedAttr(check):
  if check:
    return 'checked'
  return ''


class Selection(object):

  def __init__(self, initial, index, card):
    self.initial = initial
    self.index = index
    self.card = card

  def __cmp__(self, other):
    if self.card.kept != other.card.kept:
      return -cmp(self.card.kept, other.card.kept)
    elif self.initial != other.initial:
      return cmp(cards.SET_ORDER.index(self.initial),
                 cards.SET_ORDER.index(other.initial))
    else:
      return cmp(self.index, other.index)

  def GetFormatMap(self, card_sets):
    if self.card.name == 'Black Market':
      has_black_market = True
    card_set = card_sets[self.initial]
    icon = card_set.icon
    if self.card.icon:
      icon = self.card.icon
    return {'cost': self.card.cost,
            'potion': "/%d" % self.card.potion if self.card.potion > 0 else "",
            'icon': icon,
            'index': self.index,
            'initial': self.initial,
            'kept': _CheckedAttr(self.card.kept),
            'name': self.card.name,
            'set_name': card_set.name,
           }


class Require(object):

  def __init__(self, default_checked):
    self.full_set = None
    self.no_repeats = False

    self.costs = {}
    for cost in xrange(_MIN_COST, _MAX_COST + 1):
      self.costs[cost] = _RADIO_MAYBE

    self.plain_action = _RADIO_MAYBE

    self.pluses = {}
    for initial in cards.PLUSES.iterkeys():
      self.pluses[initial] = _RADIO_MAYBE

    self.types = {}
    for initial, type in cards.TYPES.iteritems():
      if type == cards.ACTION:
        continue
      self.types[initial] = _RADIO_MAYBE

  def Handle(self, remainder):
    try:
      section = remainder[0]
      if section == 'o':
        if remainder[1:] == 'r':
          self.no_repeats = True
          return True
        else:
          return False

      subsection = remainder[1]
      value = int(remainder[2])
      if section == 'c':
        self.costs[int(subsection)] = value
      elif section == 'l':
        self.pluses[subsection] = value
      elif section == 'p':
        if subsection == 'a':
          self.plain_action = value
        else:
          return False
      elif section == 't':
        self.types[subsection] = value
      else:
        return False
    except ValueError:
      return False
    except IndexError:
      return False
    return True

  def IsAvailable(self, card):
    if self.full_set:
      for selection in self.full_set:
        if selection.card == card:
          return False
    if self.no_repeats and card.prev_set:
      return False
    if card.cost in self.costs and self.costs[card.cost] == _RADIO_NONE:
      return False
    if self.plain_action == _RADIO_NONE and card.type == cards.ACTION.mask:
        return False
    for initial, radio_value in self.pluses.iteritems():
      if radio_value == _RADIO_NONE and card.IsPlus(cards.PLUSES[initial]):
        return False

    for initial, radio_value in self.types.iteritems():
      if radio_value == _RADIO_NONE and card.Is(cards.TYPES[initial]):
          return False
    return True


class Selector(object):

  def Select(self, available, selected):
    if len(selected) >= 10:
      return None
    return self._ChooseIndex(random.randrange(0, len(available)), available,
                             selected)

  def _ChooseIndex(self, index, available, selected):
    card = available[index]
    del available[index]
    selected.append(card)
    logging.info('Selected ' + card.card.name)
    return card


class SelectorSubset(Selector):

  def Select(self, available, selected, ignore_if_fulfilled=True):
    if len(selected) >= 10:
      return None
    if ignore_if_fulfilled:
      for selection in selected:
        if self.IsFulfilledBy(selection.card):
          return None
    subset = [i for i in xrange(len(available))
              if self.IsFulfilledBy(available[i].card)]
    if not subset:
      return None
    return self._ChooseIndex(subset[random.randrange(0, len(subset))],
                             available, selected)


class SelectorCost(SelectorSubset):
  def __init__(self, cost):      self.cost = cost
  def IsFulfilledBy(self, card): return card.cost == self.cost

class SelectorPlain(SelectorSubset):
  def __init__(self, mask):      self.mask = mask
  def IsFulfilledBy(self, card): return card.type_mask == self.mask

class SelectorPlus(SelectorSubset):
  def __init__(self, plus):      self.plus = plus
  def IsFulfilledBy(self, card): return card.IsPlus(self.plus)

class SelectorType(SelectorSubset):
  def __init__(self, type):      self.type = type
  def IsFulfilledBy(self, card): return card.Is(self.type)

class SelectorTypeExcluded(SelectorSubset):
  def __init__(self, type):      self.type = type
  def IsFulfilledBy(self, card): return not card.Is(self.type)

class SelectorNonAttack2(SelectorSubset):
  def IsFulfilledBy(self, card):
    return card.cost == 2 and not card.Is(cards.ATTACK)


def _CheckLimits(set_requirements, require, selected, available, market):
  counts = {}
  for selection in selected:
    initial = selection.initial
    if initial not in counts:
      counts[initial] = 1
    else:
      counts[initial] += 1

  total_minimum = len(selected)
  minimums = set()
  for initial, card_set in set_requirements:
    count = 0
    if initial in counts:
      count = counts[initial]

    minimum = 0
    if card_set.require_cards:
      minimum = 1
    if (count > 0 or card_set.require_cards) and card_set.require_minimum:
      minimum = card_set.minimum
    if minimum > count:
      total_minimum += minimum - count
      minimums.add(initial)

  for i in xrange(len(set_requirements) - 1, -1, -1):
    initial, card_set = set_requirements[i]
    if initial in minimums:
      continue

    remove = False
    if initial not in counts and card_set.require_minimum:
      if total_minimum + card_set.minimum > 10:
        remove = True
    elif total_minimum >= 10:
      remove = True

    if remove:
      for j in xrange(len(available) - 1, -1, -1):
        if initial == available[j].initial:
          market.append(available[j])
          del available[j]
      del set_requirements[i]


def _SelectCards(card_sets, require):
  if require.full_set:
    selected = require.full_set
    available = []
    for initial, card_set in card_sets.iteritems():
      for card_index, card in enumerate(card_set.cards):
        if card.check and require.IsAvailable(card):
          available.append(Selection(initial, card_index, card))
    return sorted(selected), available

  available = []
  selected = []
  market = []
  for initial, card_set in card_sets.iteritems():
    for card_index, card in enumerate(card_set.cards):
      if card.check:
        if card.kept:
          selected.append(Selection(initial, card_index, card))
        elif require.IsAvailable(card):
          available.append(Selection(initial, card_index, card))
  if (len(available) + len(selected) <= 10):
    return sorted(available + selected), []

  set_requirements = [x for x in card_sets.items()]

  _CheckLimits(set_requirements, require, selected, available, market)
  if require.plain_action == _RADIO_REQUIRED:
    if SelectorPlain(cards.ACTION.mask).Select(available, selected):
      _CheckLimits(set_requirements, require, selected, available, market)
  for initial, radio_value in require.pluses.iteritems():
    if radio_value == _RADIO_REQUIRED:
      if SelectorPlus(cards.PLUSES[initial]).Select(available, selected):
        _CheckLimits(set_requirements, require, selected, available, market)
  for initial, radio_value in require.types.iteritems():
    if initial != cards.INITIAL_DEFENSE and radio_value == _RADIO_REQUIRED:
      if SelectorType(cards.TYPES[initial]).Select(available, selected):
        _CheckLimits(set_requirements, require, selected, available, market)
  for cost, radio_value in require.costs.iteritems():
    if cost != 2 and radio_value == _RADIO_REQUIRED:
      if SelectorCost(cost).Select(available, selected):
        _CheckLimits(set_requirements, require, selected, available, market)

  random_selector = Selector()
  while available and len(selected) < 9:
    random_selector.Select(available, selected)
    _CheckLimits(set_requirements, require, selected, available, market)

  # This mixes the type defense and cost 2 requirements because, at present,
  # all defenses are cost 2.  If this changes in the future, it will be
  # necessary to split this logic out.
  if require.types[cards.INITIAL_DEFENSE] == _RADIO_REQUIRED:
    for selection in selected:
      if selection.card.Is(cards.DEFENSE):
        if Selector().Select(available, selected):
          _CheckLimits(set_requirements, require, selected, available, market)
        break
      elif selection.card.Is(cards.ATTACK):
        if SelectorType(cards.DEFENSE).Select(available, selected):
          _CheckLimits(set_requirements, require, selected, available, market)
        break
    if require.costs[2] == _RADIO_REQUIRED:
      if SelectorNonAttack2().Select(available, selected):
        _CheckLimits(set_requirements, require, selected, available, market)
    if SelectorTypeExcluded(cards.ATTACK).Select(available, selected,
                                                 ignore_if_fulfilled=False):
      _CheckLimits(set_requirements, require, selected, available, market)
  else:
    if require.costs[2] == _RADIO_REQUIRED:
      if SelectorCost(2).Select(available, selected):
        _CheckLimits(set_requirements, require, selected, available, market)

  random_selector.Select(available, selected)
  return sorted(selected), available + market


def _AddSelectedCards(add, card_sets, require):
  add('    <table class=selected>\n'
      '      <tr><th>Keep</th><th/><th align=left>&nbsp;Card</th></tr>\n')
  has_black_market = False
  selected_cards, black_market_cards = _SelectCards(card_sets, require)
  for selection in selected_cards:
    if selection.card.name == 'Black Market':
      has_black_market = True
    add('      <tr><td align=center>\n'
        '        <input type=hidden name=os%(initial)s%(index)d>\n'
        '        <input type=checkbox name=k%(initial)s%(index)d\n'
        '            %(kept)s/></td>\n'
        '        <td><img src=%(icon)s class=icon title=%(set_name)s></td>\n'
        '        <td>&nbsp;%(name)s (%(cost)d%(potion)s)</td>\n'
        '      </tr>\n'
        % selection.GetFormatMap(card_sets))

  for x in xrange(len(selected_cards), 10):
    if x == len(selected_cards):
      add('      <tr><td colspan=2/><td>(too few cards)</td></tr>\n')
    else:
      add('      <tr><td colspan=2>&nbsp;</td></tr>\n')
  add('    </table>\n')

  if has_black_market:
    return black_market_cards
  else:
    return None


def _AddSelectedCopper(add):
  add('    <table class=copper><tr><th>Player:&nbsp;</th><th>Copper</th></tr>\n')
  for i in xrange(6):
    random_value = random.randrange(0, 12)
    if random_value == 0:
      round1 = 2
    elif random_value < 6:
      round1 = 3
    elif random_value < 11:
      round1 = 4
    else:
      round1 = 5

    add('    <tr id=player%(num)d style=display:none>\n'
        '      <td>Player %(num)d:&nbsp;</td>\n'
        '      <td>%(round1)d and %(round2)d</td>\n'
        '    </tr>\n'
        % {'num': i + 1,
           'round1': round1,
           'round2': 7 - round1,
          })
  add('      <tr id=player_show><td colspan=2 align=center>\n'
      '          <input type=button value="Next Copper"\n'
      '              onclick=ShowNextCopper() />\n'
      '      </td></tr>\n'
      '    </table>')


def _AddBlackMarket(add, card_sets, available_cards):
  add('    <table class=market width=100%>\n'
      '      <tr><th colspan=2>Black Market</th></tr>\n'
      '      <tr>\n'
      '        <td><span id=bm0>(hidden)</span></td>\n'
      '        <td width=1%>\n'
      '          <input id=bmt0 type=button value=Take disabled\n'
      '              onclick=TakeBlackMarket(0) />\n'
      '        </td>\n'
      '      </tr>\n'
      '      <tr>\n'
      '        <td><span id=bm1>(hidden)</span></td>\n'
      '        <td width=1%>\n'
      '          <input id=bmt1 type=button value=Take disabled\n'
      '              onclick=TakeBlackMarket(1) />\n'
      '        </td>\n'
      '      </tr>\n'
      '      <tr>\n'
      '        <td><span id=bm2>(hidden)</span></td>\n'
      '        <td width=1%>\n'
      '          <input id=bmt2 type=button value=Take disabled\n'
      '              onclick=TakeBlackMarket(2) />\n'
      '        </td>\n'
      '      </tr>\n'
      '      <tr><td colspan=2 align=center>\n'
      '            <input id=bm type=button onclick=NextBlackMarket()\n'
      '                value="Show next 3"/>\n'
      '      </td></tr>\n'
      '    </table>\n'
      '    <script>\n'
      '      var black_market_offset = 0;\n')
  black_market_cards = []
  for selection in available_cards:
    black_market_cards.append(
        '<img src=%(icon)s class=icon title=%(set_name)s> '
        '%(name)s (%(cost)d%(potion)s)' % selection.GetFormatMap(card_sets))
  random.shuffle(black_market_cards)
  add('      var black_market_cards = %s;\n'
      '    </script>\n'
      % black_market_cards)


def _AddRequirementRow(add, name, marker, radio_value, required='Required'):
  add('  <tr>\n'
      '    <td>&nbsp;%(name)s:</td>\n'
      '    <td>\n'
      '      <input type=radio name=r%(marker)s %(check_none)s value=0\n'
      '          >None</input> &nbsp;\n'
      '      <input type=radio name=r%(marker)s %(check_maybe)s value=1\n'
      '          >Maybe</input> &nbsp;\n'
      '      <input type=radio name=r%(marker)s %(check_req)s value=2\n'
      '          >%(required)s</input>\n'
      '    </td>\n'
      '  </tr>\n'
      % {'check_maybe': _CheckedAttr(radio_value == _RADIO_MAYBE),
         'check_none': _CheckedAttr(radio_value == _RADIO_NONE),
         'check_req': _CheckedAttr(radio_value == _RADIO_REQUIRED),
         'marker': marker,
         'name': name,
         'required': required,
        })


def _AddRequirements(add, require):
  add('  <tr>\n'
      '    <td class="full_border zippy" align=left colspan=3>\n'
      '      <a class=zippy href=javascript:ExpandSet("r")>\n'
      '      <img id=r_zippy src=static/zippy_expand.png />\n'
      '      <span><span id=r_expand>Expand</span>\n'
      '      general requirements</span>\n'
      '      </a>\n'
      '    </td>\n'
      '  </tr>\n')
  add('  <tr name=r_row style=display:none><td class="left right" colspan=3>\n'
      '      <input type=checkbox name=ror %(check)s>Don\'t use the same\n'
      '           card twice in a row.\n'
      '      </input>\n'
      '  </td></tr>\n'
      % {'check': _CheckedAttr(require.no_repeats)})

  add('  <tr name=r_row style=display:none><td class="left right" colspan=3>\n'
      '    <table width=100%>\n')
  for initial in sorted(cards.TYPES.iterkeys()):
    type = cards.TYPES[initial]
    if type == cards.ACTION:
      _AddRequirementRow(add, 'Plain action', 'pa', require.plain_action)
    else:
      marker = 't' + initial
      require_type = require.types[initial]
      if type != cards.DEFENSE:
        _AddRequirementRow(add, type.name, marker, require_type)
      else:
        _AddRequirementRow(add, type.name, marker, require_type,
                           required='If attacks are present')

  for initial in sorted(cards.PLUSES.iterkeys()):
    plus = cards.PLUSES[initial]
    _AddRequirementRow(add, plus.name, 'l' + initial, require.pluses[initial])

  for cost in xrange(_MIN_COST, _MAX_COST + 1):
    _AddRequirementRow(add, 'Cost %d' % cost, 'c%d' % cost, require.costs[cost])
  add('  </table></td></tr>\n')

  add('  <tr name=r_row style=display:none>\n'
      '    <td class="left bottom right" colspan=3><span>&nbsp;Note:\n'
      '      Adding too many requirements may result in<br>\n'
      '      &nbsp;unfulfilled requirements.\n'
      '    </span></td>\n'
      '  </tr>\n')


def _AddCardSets(add, card_sets):
  for initial in cards.SET_ORDER:
    if initial not in card_sets:
      continue
    card_set = card_sets[initial]
    add('  <tr><td colspan=3 class=vspacer></td></tr>\n')
    add('  <tr>\n'
        '    <th colspan=3 class=full_border align=left>\n'
        '      <input type=checkbox %(checked)s\n'
        '          onclick=ToggleSet("%(initial)s",%(num_cards)d)\n'
        '          id=set%(initial)s />\n'
        '      <img src=%(icon)s class=icon> %(link)s\n'
        '    </th>\n'
        '  </tr>\n'
        '  <tr>\n'
        '    <td class="full_border zippy" align=left colspan=3>\n'
        '      <a class=zippy href=javascript:ExpandSet("%(initial)s")>\n'
        '      <img id=%(initial)s_zippy src=static/zippy_expand.png />\n'
        '      <span><span id=%(initial)s_expand>Expand</span>\n'
        '      card selection</span>\n'
        '      </a>\n'
        '    </td>\n'
        '  </tr>\n'
        % {'checked': _CheckedAttr(card_set.check),
           'icon': card_set.icon,
           'initial': initial,
           'link': card_set.link,
           'num_cards': card_set.num_cards,
          })
    num_rows = int(math.ceil(float(card_set.num_cards) / _NUM_COLS))
    for row in xrange(0, num_rows):
      add('  <tr name=%s_row style=display:none>\n' % initial)
      for col in xrange(0, _NUM_COLS):
        classes = ''
        if col == 0:
          classes = 'left'
        elif col == _NUM_COLS - 1:
          classes = 'right'
        if row == num_rows - 1:
          classes += ' bottom'
        add('    <td class="%s">\n' % classes)

        card_index = row + col * num_rows
        if card_index < card_set.num_cards:
          card = card_set.cards[card_index]
          add('      <input type=checkbox %(check)s\n'
              '          name=%(set_initial)s%(card_index)d\n'
              '          %(is_attack)s>%(card_name)s</input>\n'
              % {'set_initial': initial, 'card_index': card_index,
                 'is_attack': card.IsAttackAttr(), 'card_name': card.name,
                 'check': _CheckedAttr(card.check)})
        add('    </td>\n')
      add('  </tr>\n')
    add('  <tr>\n'
        '    <td class="full_border zippy" align=left colspan=3>\n'
        '      <a class=zippy href=javascript:ExpandSet("%(initial)sr")>\n'
        '      <img id=%(initial)sr_zippy src=static/zippy_expand.png />\n'
        '      <span><span id=%(initial)sr_expand>Expand</span>\n'
        '      set requirements</span></a>\n'
        '    </td>\n'
        '  </tr>\n'
        '  <tr name=%(initial)sr_row style=display:none>\n'
        '    <td colspan=3 class="left top right">\n'
        '      <input type=checkbox name=%(initial)src %(require_cards)s/>\n'
        '      Require cards from %(name)s.\n'
        '    </td>\n'
        '  </tr>\n'
        '  <tr name=%(initial)sr_row style=display:none>\n'
        '    <td colspan=3 class="left bottom right">\n'
        '      <input type=checkbox name=%(initial)srmc %(require_min)s/>\n'
        '      If present, require at least\n'
        '      <select name=%(initial)srmv>\n'
        % {'initial': initial,
           'name': card_set.name,
           'require_cards': _CheckedAttr(card_set.require_cards),
           'require_min': _CheckedAttr(card_set.require_minimum),
          })
    for x in xrange(1, 11):
      if x == card_set.minimum:
        add('        <option value=%d selected>%d</option>\n' % (x, x))
      else:
        add('        <option value=%d>%d</option>\n' % (x, x))
    add('      </select>\n'
        '      cards from %s.\n'
        '    </td>\n'
        '  </tr>\n'
        % card_set.name)


def _GeneratePage(user, add, prev_set_attr, all_check,
                  card_sets, require):
  add('<html><head>\n'
      '  <link rel=stylesheet type="text/css" href="static/main.css">\n'
      '  <script type="text/javascript" src="static/main.js"></script>\n'
      '<title>Dominion Card Selector</title></head><body>\n'
      '<form name=input action=/ method=post>\n'
      '<table class=main>\n'
      '  <col class=primary_col />\n'
      '  <col class=primary_col />\n'
      '  <col class=primary_col />\n'
      '  <tr><td colspan=3><table width=100%>\n'
      '    <tr><td valign=top rowspan=2 width=50%>\n')
  black_market_cards = _AddSelectedCards(add, card_sets, require)
  add('    <center>\n'
      '      <input type=submit value="New Round"/> &nbsp;\n')
  add('      <input type=submit value="Previous Set" %s />\n' % prev_set_attr)
  add('    </center>\n'
      '  </td>\n'
      '  <td valign=top>')
  _AddSelectedCopper(add)
  add('  </td></tr>\n'
      '  <tr><td valign=bottom>\n')
  if black_market_cards:
    _AddBlackMarket(add, card_sets, black_market_cards)
  add('  </td></tr></table></td></tr>\n'
      '  <tr><td colspan=3 class=minispacer></td></tr>\n')
  if user:
    add('  <tr>\n'
        '    <td><a href=/set_editor>Edit custom sets</a>\n'
        '    <td colspan=2 align=right>\n'
        '      %s | <a href=%s>Sign out</a>\n'
        '    </td>\n'
        '  </tr>\n'
        % (user.email(), users.create_logout_url(_URI)))
  else:
    add('  <tr><td colspan=3 align=right>\n'
        '    <a href=%s>Sign in</a> to save settings and add custom sets\n'
        '  </td></tr>\n'
        % users.create_login_url(_URI))

  add('  <tr><td colspan=3 class=minispacer></td></tr>\n')
  _AddRequirements(add, require)
  _AddCardSets(add, card_sets)
  add('</table>\n'
      '</form></body></html>\n')


def _SafeGetCard(card_sets, initial, remainder):
  if initial not in card_sets:
    return None, None, None
  card_set = card_sets[initial]
  try:
    card_index = int(remainder)
  except ValueError:
    return None, None, None
  if card_index < 0 or card_index >= len(card_set.cards):
    return None, None, None
  return card_set, card_index, card_set.cards[card_index]


def _UseSettings(user, add, saved_settings, post_vars, store_changes):
  if saved_settings:
    logging.info(
        'Loaded settings: key_name=%s, email=%s, last_access=%s, vars=%s,'
        ' checked_cards=%s'
        % (user.user_id(), saved_settings.email, saved_settings.last_access,
           saved_settings.vars, saved_settings.checked_cards))

  all_check = False
  if post_vars:
    check = False
  else:
    check = True
  card_sets = cards.CreateSets(check)
  require = Require(False)

  # Load any custom cards.
  if saved_settings:
    for set_index, set in ((0, saved_settings.set0),
                           (1, saved_settings.set1),
                           (2, saved_settings.set2),
                           (3, saved_settings.set3),
                           (4, saved_settings.set4),
                           (5, saved_settings.set5),
                           (6, saved_settings.set6),
                           (7, saved_settings.set7),
                           (8, saved_settings.set8),
                           (9, saved_settings.set9),
                          ):
      if not set:
        continue
      set_cards = []
      for card_parts in set[1:]:
        set_cards.append(cards.CustomCard(card_parts, check))
      card_sets[str(set_index)] = cards.CardSet(set[0], cards.UNKNOWN_ICON,
                                                check, set[0], set_cards)

  # Load checked cards from saved settings if this isn't a post.
  if check:
    for set_checked in saved_settings.checked_cards:
      initial = set_checked[0]
      if initial not in card_sets:
        continue
      card_set = card_sets[initial]

      for i in xrange(min(len(card_set.cards), len(set_checked) - 1)):
        if set_checked[i + 1] == '0':
          card_set.cards[i].check = False
      card_set.check = any([x.check for x in card_set.cards])

  # Load all post variables (or saved variables).
  prev_set = []
  used_vars = []
  if not post_vars:
    post_vars = saved_settings.vars
  for name in post_vars:
    if len(name) < 2:
      logging.warning('Unused name: %s' % name)
      continue

    initial = name[0]
    remainder = name[1:]

    used = False
    if initial == _INITIAL_KEEP:
      _, _, card = _SafeGetCard(card_sets, remainder[0], remainder[1:])
      if card:
        card.kept = True
        used = True
    elif initial == _INITIAL_OPTION:
      sub_initial = remainder[0]
      if sub_initial == 'f':
        full_set = []
        for item in remainder[1:].split(',')[:-1]:
          set_initial = item[0]
          _, card_index, card = _SafeGetCard(card_sets, item[0], item[1:])
          full_set.append(Selection(set_initial, card_index, card))
        require.full_set = full_set
      elif sub_initial == 's':
        _, _, card = _SafeGetCard(card_sets, remainder[1], remainder[2:])
        if card:
          card.prev_set = True
          prev_set.append(remainder[1:])
          used = True
    elif initial == _INITIAL_REQUIREMENT:
      if require.Handle(remainder):
        used = True
    else:
      if remainder[0] == 'r':
        if initial not in card_sets:
          continue
        card_set = card_sets[initial]
        req_initial = remainder[1]
        if req_initial == 'c':
          card_set.require_cards = True
          used = True
        elif req_initial == 'm':
          if remainder[2] == 'c':
            card_set.require_minimum = True
            used = True
          elif remainder[2] == 'v':
            card_set.minimum = int(remainder[3:])
            used = True
      else:
        card_set, _, card = _SafeGetCard(card_sets, initial, remainder)
        if card:
          card.check = True
          card_set.check = True

    if used:
      used_vars.append(name)

  # Save changes.
  if user and store_changes:
    checked_cards = []
    for initial, card_set in card_sets.iteritems():
      set_checked = [initial]
      for card in card_set.cards:
        if card.check:
          set_checked.append('1')
        else:
          set_checked.append('0')
      checked_cards.append(''.join(set_checked))

    if not saved_settings:
      saved_settings = data.Settings(key_name=user.user_id(),
                                     email=user.email())
    saved_settings.last_access = datetime.datetime.now()
    saved_settings.vars = used_vars
    saved_settings.checked_cards = checked_cards
    logging.info(
        'Saving settings: key_name=%s, email=%s, last_access=%s, vars=%s,'
        ' checked_cards=%s'
        % (user.user_id(), saved_settings.email, saved_settings.last_access,
           saved_settings.vars, saved_settings.checked_cards))
    saved_settings.put()

  # Prepare the previous set data.
  if prev_set:
    prev_set.append('')
    prev_set_attr = 'name=of' + ','.join(prev_set)
  else:
    prev_set_attr = 'disabled'

  # Generate the page.
  all_check = any([x.check for x in card_sets.itervalues()])
  _GeneratePage(user, add, prev_set_attr, all_check, card_sets, require)


class MainPage(webapp.RequestHandler):

  def get(self):
    random.seed()
    user = users.get_current_user()
    if user:
      saved_settings = data.Settings.get_by_key_name(user.user_id())
      if saved_settings:
        _UseSettings(user, self.response.out.write, saved_settings,
                     None, False)
        return
    _GeneratePage(user, self.response.out.write, 'disabled',
                  True, cards.CreateSets(True), Require(True))

  def post(self):
    random.seed()
    user = users.get_current_user()
    compressed_settings = []
    for key, value in self.request.postvars.iteritems():
      if value == 'on':
        compressed_settings.append(key)
      else:
        compressed_settings.append(key + value)
    if user:
      saved_settings = data.Settings.get_by_key_name(user.user_id())
    else:
      saved_settings = None
    _UseSettings(users.get_current_user(), self.response.out.write,
                 saved_settings, compressed_settings, True)


def main():
  application = webapp.WSGIApplication([('/', MainPage),
                                        ('/set_editor', set_editor.SetEditor),
                                       ], debug=True)
  util.run_wsgi_app(application)


if __name__ == '__main__':
  main()
